Make xasprintf work sensibly in light of both (sigh) C99 and SUSv1 definitions
authorrobertl <robertl>
Thu, 5 Oct 2006 04:07:34 +0000 (04:07 +0000)
committerrobertl <robertl>
Thu, 5 Oct 2006 04:07:34 +0000 (04:07 +0000)
of snprintf.  Probably needs more abstraction work, but this is more correct
in more cases.

util.c

diff --git a/util.c b/util.c
index d2e48bd4a9884296867396555b4cac1e5dfe8ef8..7dfcb446d10a19d45bb17461c492ad4c3b0983cd 100644 (file)
--- a/util.c
+++ b/util.c
@@ -293,21 +293,75 @@ xfputs(const char *errtxt, const char *s, FILE *stream)
 int
 xasprintf(char **strp, const char *fmt, ...)
 {
+       /* From http://perfec.to/vsnprintf/pasprintf.c */
+/* size of first buffer malloc; start small to exercise grow routines */
+#define        FIRSTSIZE       64
        va_list args;
-       int res;
-       
-       va_start(args, fmt);
-       res = vsnprintf((char *)NULL, 0, fmt, args);
-       *strp = xmalloc(res + 1);
-       va_end(args);
-       
-       va_start(args, fmt);
-       res = vsnprintf(*strp, res + 1, fmt, args);
-       va_end(args);
+       char *buf;
+       size_t bufsize;
+       char *newbuf;
+       size_t nextsize;
+       int outsize;
+
+       bufsize = 0;
+       for (;;) {
+               if (bufsize == 0) {
+                       if ((buf = xmalloc(FIRSTSIZE)) == NULL) {
+                               *strp = NULL;
+                               return -1;
+                       }
+                       bufsize = 1;
+               } else if ((newbuf = xrealloc(buf, nextsize)) != NULL) {
+                       buf = newbuf;
+                       bufsize = nextsize;
+               } else {
+                       xfree(buf);
+                       *strp = NULL;
+                       return -1;
+               }
 
-       is_fatal(res < 0, "(internal): vsnprintf returned %d!", res);
-       
-       return res;
+               va_start(args, fmt);
+               outsize = vsnprintf(buf, bufsize, fmt, args);
+               va_end(args);
+
+               if (outsize == -1) {
+                       /* Clear indication that output was truncated, but no
+                        * clear indication of how big buffer needs to be, so
+                        * simply double existing buffer size for next time.
+                        */
+                       nextsize = bufsize * 2;
+
+               } else if (outsize == bufsize) {
+                       /* Output was truncated (since at least the \0 could
+                        * not fit), but no indication of how big the buffer
+                        * needs to be, so just double existing buffer size
+                        * for next time.
+                        */
+                       nextsize = bufsize * 2;
+
+               } else if (outsize > bufsize) {
+                       /* Output was truncated, but we were told exactly how
+                        * big the buffer needs to be next time. Add two chars
+                        * to the returned size. One for the \0, and one to
+                        * prevent ambiguity in the next case below.
+                        */
+                       nextsize = outsize + 2;
+
+               } else if (outsize == bufsize - 1) {
+                       /* This is ambiguous. May mean that the output string
+                        * exactly fits, but on some systems the output string
+                        * may have been trucated. We can't tell.
+                        * Just double the buffer size for next time.
+                        */
+                       nextsize = bufsize * 2;
+
+               } else {
+                       /* Output was not truncated */
+                       break;
+               }
+       }
+       *strp = buf;
+       return 0;
 }
 
 /*